<?php
/**
 * Created by PhpStorm.
 * User: whwyy
 * Date: 2018/3/30 0030
 * Time: 14:39
 */

namespace Database;



/**
 * Class ActiveRecord
 * @package Database
 *
 * @property $attributes
 * @property-read $oldAttributes
 * @method getPrimary()
 * @method beforeSearch($model)
 */
class ActiveRecord extends \Database\base\ActiveRecord
{

    /**
     * @return array
     */
    public function rules()
    {
        return [];
    }

    /**
     * @return array
     */
    public function getAttributes()
    {
        return $this->_attributes;
    }

    /**
     * @return array
     */
    public function getOldAttributes()
    {
        return $this->_oldAttributes;
    }

    /**
     * @param $name
     * @param $value
     * @return mixed
     */
    public function setAttribute($name, $value)
    {
        return $this->_attributes[$name] = $value;
    }

    /**
     * @param $name
     * @param $value
     * @return mixed
     */
    public function setOldAttribute($name, $value)
    {
        return $this->_oldAttributes[$name] = $value;
    }

    /**
     * @param array $param
     * @return $this
     */
    public function setAttributes(array $param)
    {
        if (empty($param)) {
            return $this;
        }
        foreach ($param as $key => $val) {
            $this->$key = $val;
        }
        return $this;
    }


    /**
     * @return array|mixed
     * @throws \Exception
     */
    public function toArray()
    {
        $data = $this->_attributes;
        if (!empty($this->_relate) && is_array($this->_relate)) {
            foreach ($this->_relate as $key => $val) {
                /**
                 * @var ActiveRecord|string $val ;
                 * @var ActiveRecord|string $relate ;
                 */
                if (is_object($val)) {
                    if (method_exists($val, 'toArray')) {
                        $data[$key] = $val->toArray();
                    } else {
                        $data[$key] = get_object_vars($val);
                    }
                } else if (is_string($val)) {
                    if (method_exists($this, $val)) {
                        $relate = $this->{$val}()->get();
                        if (empty($relate)) {
                            $data[$key] = $relate;
                        } else {
                            $data[$key] = $relate->toArray();
                        }
                    } else {
                        $data[$key] = $val;
                    }
                } else {
                    $data[$key] = $val;
                }
            }
        }
        return $data;
    }

    /**
     * @param $name
     * @param $value
     * @throws \Exception
     */
    public function __set($name, $value)
    {
        if ($this->has($name)) {
            $this->setAttribute($name, $value);
        } else if (property_exists($this, $name)) {
            $this->$name = $value;
        } else {
            parent::__set($name, $value);
        }
    }

    /**
     * @param $name
     * @return mixed|null
     * @throws \Exception
     */
    public function __get($name)
    {
        $method = 'get' . ucfirst($name);
        if ($this->has($name)) {
            return $this->_attributes[$name] ?? NULL;
        } else if (property_exists($this, $name)) {
            return $this->$name;
        } else if (method_exists($this, $method)) {
            return $this->resolve($this->$method());
        } else if (isset($this->_relate[$name])) {
            return $this->resolve($this->_relate[$name]);
        } else {
            return parent::__get($name);
        }
    }

    /**
     * @param $name
     * @return array|null|ActiveRecord
     * @throws \Exception
     */
    private function resolve($call)
    {
        if ($call instanceof HasOne) {
            return $call->get();
        } else if ($call instanceof HasMany) {
            return $call->get();
        } else {
            return $call;
        }
    }


    /**
     * @param $modelName
     * @param $foreignKey
     * @param $localKey
     * @return mixed|Model
     * @throws \Exception
     */
    public function hasOne($modelName, $foreignKey, $localKey)
    {
        if (!$this->has($localKey)) {
            throw new \Exception("Need join table primary key.");
        }
        return new HasOne($modelName, $foreignKey, $localKey, $this->getAttribute($localKey));
    }


    /**
     * @param $modelName
     * @param $foreignKey
     * @param $localKey
     * @return mixed|Model
     * @throws \Exception
     */
    public function hasMany($modelName, $foreignKey, $localKey)
    {
        if (!$this->has($localKey)) {
            throw new \Exception("Need join table primary key.");
        }
        return new HasMany($modelName, $foreignKey, $localKey, $this->getAttribute($localKey));
    }

    /**
     * @param mixed $offset
     * @return bool
     * @throws \Exception
     */
    public function offsetExists($offset)
    {
        return $this->has($offset);
    }

    /**
     * @param mixed $offset
     * @return mixed|null
     * @throws \Exception
     */
    public function offsetGet($offset)
    {
        return $this->__get($offset);
    }

    /**
     * @param mixed $offset
     * @param mixed $value
     * @throws \Exception
     */
    public function offsetSet($offset, $value)
    {
        return $this->__set($offset, $value);
    }

    /**
     * @param mixed $offset
     * @throws \Exception
     */
    public function offsetUnset($offset)
    {
        if (!$this->has($offset)) {
            return;
        }
        unset($this->_attributes[$offset]);
        unset($this->_oldAttributes[$offset]);
        if (isset($this->_relate)) {
            unset($this->_relate[$offset]);
        }
    }

    /**
     * @return array
     */
    public function getRelate()
    {
        return $this->_relate;
    }


    /**
     * @return bool
     * @throws \ReflectionException
     * @throws \Yoc\Exception\DbException
     */
    public function afterDelete()
    {
        if (!$this->hasPrimary()) {
            return TRUE;
        }
        $value = $this->{$this->getPrimary()};
        if (empty($value)) {
            return TRUE;
        }
        $redis = \Yoc::$app->redis;
        if ($redis->exists($this->getUniqueCachePrimary())) {
            $redis->delete($this->getUniqueCachePrimary());
        }
        return TRUE;
    }

    /**
     * @throws \ReflectionException
     * @throws \Yoc\Exception\DbException
     */
    public function unset()
    {
        $fields = func_get_args();
        $fields = array_shift($fields);
        if (!is_array($fields)) {
            $fields = explode(',', $fields);
        }
        foreach ($fields as $val) {
            if (!$this->has($val)) {
                continue;
            }
            unset($this->_attributes[$val], $this->_oldAttributes[$val]);
        }
        return $this;
    }

    /**
     * @param ActiveRecord $model
     * @param $data
     * @return ActiveRecord
     */
    public static function propute(ActiveRecord $model, array $data)
    {
        $model = clone $model;
        $model->attributes = $data;
        return $model;
    }
}